/**
 * Copyright (c) 2004-2015 All Rights Reserved.
 */
package com.zbest.component.session;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 钱滚滚分布式session，使用可以参考component-session.xml中的配置
 * 
 *
 * @author czhou3306@gmail.com
 * @version $Id: SessionRepositoryFilter.java, v0.1 2015年6月9日 上午10:32:04 czhou3306@gmail.com Exp $
 */
public class QianSessionFilter extends OncePerRequestFilter {

    /**logger*/
    private static final Logger logger              = LoggerFactory
                                                        .getLogger(SessionConstant.SESSION_LOGGER_NAME);

    /**session的仓储*/
    private SessionRepository   sessionRepository;

    private HttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();

    /**
     * 
     * @see com.zbest.component.session.OncePerRequestFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
     */
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        QianHttpRequestWrapper wrappedRequest = new QianHttpRequestWrapper(request, response,
            request.getSession().getServletContext());
        String sessionId = null;
        try {
            long now = System.currentTimeMillis();
            wrappedRequest.getSession();
            if (wrappedRequest.currentSession.isNew()) {
                //如果是新建的session，需要把sessionId通过cookie带回去
                httpSessionStrategy.onNewSession(wrappedRequest.currentSession.getSession(),
                    request, response, sessionRepository.getDomain(), sessionRepository.getPath());
            }
            sessionId = wrappedRequest.currentSession.getSession().getSessionId();
            logger.info(sessionId + ",get," + (System.currentTimeMillis() - now) + "ms");

            filterChain.doFilter(wrappedRequest, response);
        } finally {
            long now = System.currentTimeMillis();
            wrappedRequest.commitSession();
            logger.info(sessionId + ",commit," + (System.currentTimeMillis() - now) + "ms");
        }

        /* 
         * 以下的这种方式，在jetty下hello1，hello2都能输入到浏览器，在tomcat下只有hello1输出到浏览器。
         * 找不到原因，所以改成上面这种，先输入sessionId到reponse里，然后再执行filter链
        try {
           Cookie c = new Cookie("hello1", "world1");
           response.addCookie(c);
           filterChain.doFilter(request, response);
        } finally {
           Cookie c = new Cookie("hello2", "world2");
           response.addCookie(c);
        }*/
    }

    /** 
     * @see org.springframework.core.Ordered#getOrder()
     */
    @Override
    public int getOrder() {
        return SessionConstant.SESSION_FILTER_ORDER;
    }

    private class QianHttpRequestWrapper extends HttpServletRequestWrapper {

        /**当前会话*/
        private HttpSessionWrapper        currentSession;
        private final HttpServletResponse response;
        private final ServletContext      servletContext;

        /**
         * 构造器
         * @param request
         * @param response
         * @param servletContext
         * @param sessionRepository
         */
        public QianHttpRequestWrapper(HttpServletRequest request, HttpServletResponse response,
                                      ServletContext servletContext) {
            super(request);
            this.response = response;
            this.servletContext = servletContext;
        }

        @Override
        public HttpSession getSession(boolean create) {
            if (currentSession != null) {
                return currentSession;
            }
            //获取sessionId
            String sessionId = getRequestedSessionId();
            if (!StringUtils.isBlank(sessionId)) {
                QianSession session = sessionRepository.getSession(sessionId);
                if (session != null) {
                    currentSession = new HttpSessionWrapper(session, getServletContext());
                    currentSession.setNew(false);
                    return currentSession;
                }
            }
            if (!create) {
                return null;
            }
            QianSession session = sessionRepository.create();
            currentSession = new HttpSessionWrapper(session, getServletContext());

            return currentSession;
        }

        public ServletContext getServletContext() {
            if (servletContext != null) {
                return servletContext;
            }
            return super.getServletContext();
        }

        @Override
        public HttpSession getSession() {
            return getSession(true);
        }

        @Override
        public String getRequestedSessionId() {
            return httpSessionStrategy.getRequestedSessionId((HttpServletRequest) getRequest());
        }

        /**
         * Allows creating an HttpSession from a Session instance.
         *
         * @author Rob Winch
         * @since 1.0
         */
        private final class HttpSessionWrapper implements HttpSession {
            private final QianSession    session;
            private final ServletContext servletContext;
            private boolean              invalidated;
            private boolean              isNew;
            private boolean              needCommit;

            public HttpSessionWrapper(QianSession session, ServletContext servletContext) {
                this.session = session;
                this.servletContext = servletContext;
                this.isNew = true;
                this.needCommit = true;
            }

            /**
             * Getter method for property <tt>session</tt>.
             * 
             * @return property value of session
             */
            public QianSession getSession() {
                return session;
            }

            public long getCreationTime() {
                checkState();
                return session.getGmtCreate();
            }

            public String getId() {
                return session.getSessionId();
            }

            public long getLastAccessedTime() {
                checkState();
                return session.getGmtLastAccess();
            }

            public ServletContext getServletContext() {
                return servletContext;
            }

            public void setMaxInactiveInterval(int interval) {
                session.setMaxInactiveInterval(interval);
            }

            public int getMaxInactiveInterval() {
                return session.getMaxInactiveInterval();
            }

            public Object getAttribute(String name) {
                checkState();
                return session.getAttribute(name);
            }

            public Object getValue(String name) {
                checkState();
                return getAttribute(name);
            }

            public Enumeration<String> getAttributeNames() {
                checkState();
                return session.getAttributeNames();
            }

            public String[] getValueNames() {
                checkState();
                return session.getValueNames();
            }

            public void setAttribute(String name, Object value) {
                checkState();
                session.setAttribute(name, value);
                needCommit = true;
            }

            public void putValue(String name, Object value) {
                checkState();
                setAttribute(name, value);
                needCommit = true;
            }

            public void removeAttribute(String name) {
                checkState();
                session.removeAttribute(name);
                needCommit = true;
            }

            public void removeValue(String name) {
                checkState();
                session.removeAttribute(name);
                needCommit = true;
            }

            public void invalidate() {
                checkState();
                this.invalidated = true;
            }

            public void setNew(boolean isNew) {
                this.isNew = isNew;
            }

            public boolean isNew() {
                return isNew;
            }

            private void checkState() {
                if (invalidated) {
                    throw new IllegalStateException("The HttpSession has already be invalidated.");
                }
            }

            /**
             * Getter method for property <tt>invalidated</tt>.
             * 
             * @return property value of invalidated
             */
            public boolean isInvalidated() {
                return invalidated;
            }

            /**
             * Getter method for property <tt>needCommit</tt>.
             * 
             * @return property value of needCommit
             */
            public boolean isNeedCommit() {
                return needCommit;
            }

            /** 
             * @see javax.servlet.http.HttpSession#getSessionContext()
             */
            @SuppressWarnings("deprecation")
            @Override
            public HttpSessionContext getSessionContext() {
                return (HttpSessionContext) servletContext;
            }
        }

        /**
         * Uses the HttpSessionStrategy to write the session id tot he response and persist the Session.
         */
        private void commitSession() {
            String sessionId = null;
            try {
                if (currentSession == null) {
                    return;
                }

                QianSession qianSession = currentSession.session;
                sessionId = qianSession.getSessionId();
                if (currentSession.isNeedCommit()) {
                    //如果是新建的session或者增加删除数据后都需要重新保存
                    sessionRepository.save(qianSession);
                } else {
                    sessionRepository.reclocking(qianSession);
                }

                if (currentSession.isInvalidated()) {
                    httpSessionStrategy.onInvalidateSession((HttpServletRequest) this.getRequest(),
                        response);
                    sessionRepository.delete(qianSession.getSessionId());
                    currentSession = null;
                }
            } catch (Exception e) {
                logger.error("session提交异常sessionId=" + sessionId, e);
            }
        }
    }

    /**
     * Setter method for property <tt>sessionRepository</tt>.
     * 
     * @param sessionRepository value to be assigned to property sessionRepository
     */
    public void setSessionRepository(SessionRepository sessionRepository) {
        this.sessionRepository = sessionRepository;
    }

    /**
     * Setter method for property <tt>httpSessionStrategy</tt>.
     * 
     * @param httpSessionStrategy value to be assigned to property httpSessionStrategy
     */
    public void setHttpSessionStrategy(HttpSessionStrategy httpSessionStrategy) {
        this.httpSessionStrategy = httpSessionStrategy;
    }

}
